package com.xframe.xdal.core.util.redis.command;

import com.xframe.xdal.core.util.StringUtil;
import com.xframe.xdal.core.util.redis.pool.IRedisPool;
import redis.clients.jedis.commands.JedisCommands;
import redis.clients.jedis.commands.MultiKeyCommands;

import java.util.List;

public class ListCommand {

    private IRedisPool pool = null;

    public ListCommand(IRedisPool pool){
        this.pool = pool;
    }

    /**
     * 返回列表 key 的长度。如果 key 不存在，则 key 被解释为一个空列表，返回 0 .
     * 如果 key 不是列表类型，返回一个错误。
     *
     * @param key   key
     *
     * @return 列表 key 的长度。
     */
    public Long llen(String key) {
        if (StringUtil.isValueNull(key)) {
            return 0L;
        }
        JedisCommands client = pool.getClient();
        try {
            return client.llen(key);
        } finally {
            pool.close(client);
        }
    }


    /**
     * 将一个或多个值 value 插入到列表 key 的表头
     * 如果有多个 value 值，那么各个 value 值按从左到右的顺序依次插入到表头：
     * 比如说，对空列表 mylist 执行命令 LPUSH mylist a b c ，列表的值将是 c b a ，
     * 这等同于原子性地执行 LPUSH mylist a 、 LPUSH mylist b 和 LPUSH mylist c 三个命令。
     * 如果 key 不存在，一个空列表会被创建并执行 LPUSH 操作，当 key 存在但不是列表类型时，返回一个错误。
     *
     * @param key   key
     * @param values   值数组
     *
     * @return 执行 LPUSH 命令后，列表的长度。
     */
    public Long lpush(String key,String... values) {
        if (StringUtil.isValueNull(key,values)) {
            return null;
        }
        JedisCommands client = pool.getClient();
        try {
            return client.lpush(key,values);
        } finally {
            pool.close(client);
        }
    }

    /**
     * 将一个或多个值 value 插入到列表 key 的表尾
     * 如果有多个 value 值，那么各个 value 值按从左到右的顺序依次插入到表尾：比如对一个空列表 mylist 执行 RPUSH mylist a b c ，得出的
     * 结果列表为 a b c ，等同于执行命令 RPUSH mylist a 、 RPUSH mylist b 、 RPUSH mylist c
     * 如果 key 不存在，一个空列表会被创建并执行 RPUSH 操作。
     * 当 key 存在但不是列表类型时，返回一个错误。
     *
     * @param key   key
     * @param values   值数组
     *
     * @return 执行 RPUSH 操作后，表的长度。
     */
    public Long rpush(String key,String... values) {
        if (StringUtil.isValueNull(key,values)) {
            return null;
        }
        JedisCommands client = pool.getClient();
        try {
            return client.rpush(key,values);
        } finally {
            pool.close(client);
        }
    }

    /**
     * 将值 value 插入到列表 key 的表头，当且仅当 key 存在并且是一个列表。
     * 当 key 不存在时， LPUSHX 命令什么也不做。
     *
     * @param key   key
     * @param value   值数组
     *
     * @return 命令执行之后，表的长度。
     */
    public Long lpushx(String key,String value) {
        if (StringUtil.isValueNull(key,value)) {
            return null;
        }
        JedisCommands client = pool.getClient();
        try {
            return client.lpushx(key,value);
        } finally {
            pool.close(client);
        }
    }

    /**
     * 将值 value 插入到列表 key 的表尾，当且仅当 key 存在并且是一个列表。
     * 当 key 不存在时， RPUSHX 命令什么也不做。
     *
     * @param key   key
     * @param value   值数组
     *
     * @return RPUSHX 命令执行之后，表的长度
     */
    public Long rpushx(String key,String value) {
        if (StringUtil.isValueNull(key,value)) {
            return null;
        }
        JedisCommands client = pool.getClient();
        try {
            return client.rpushx(key,value);
        } finally {
            pool.close(client);
        }
    }

    /**
     * 返回列表 key 中指定区间内的元素，区间以偏移量 start 和 stop 指定
     * 下标(index)参数 start 和 stop 都以 0 为底，也就是说，以 0 表示列表的第一个元素，以 1 表示列表的第二个元素
     * 可以使用负数下标，以 -1 表示列表的最后一个元素， -2 表示列表的倒数第二个元素，以此类推
     * 假如你有一个包含一百个元素的列表，对该列表执行 LRANGE list 0 10 ，结果是一个包含11个元素的列表，这表明 stop 下标也在 LRANGE 命令的取值范围之内(闭区间)
     *
     * @param key   key
     * @param start   开始的下标
     * @param stop   结束的下标
     *
     * @return 一个列表，包含指定区间内的元素
     */
    public List<String> lrange(String key,long start,long stop) {
        if (StringUtil.isValueNull(key)) {
            return null;
        }
        JedisCommands client = pool.getClient();
        try {
            return client.lrange(key,start,stop);
        } finally {
            pool.close(client);
        }
    }

    /**
     * 根据参数 count 的值，移除列表中与参数 value 相等的元素
     * count > 0 : 从表头开始向表尾搜索，移除与 value 相等的元素，数量为 count
     * count < 0 : 从表尾开始向表头搜索，移除与 value 相等的元素，数量为 count 的绝对值
     * count = 0 : 移除表中所有与 value 相等的值
     *
     * @param key   key
     * @param count   方向
     * @param value   查找的值
     *
     * @return 被移除元素的数量
     */
    public Long lrem(String key,long count,String value) {
        if (StringUtil.isValueNull(key)) {
            return 0L;
        }
        JedisCommands client = pool.getClient();
        try {
            return client.lrem(key,count,value);
        } finally {
            pool.close(client);
        }
    }


    /**
     * 命令 RPOPLPUSH 在一个原子时间内，执行以下两个动作：
     * 将列表 source 中的最后一个元素(尾元素)弹出，并返回给客户端。
     * 将 source 弹出的元素插入到列表 destination ，作为 destination 列表的的头元素。如果 source 不存在，值 nil 被返回，并且不执行其他动作
     * 如果 source 和 destination 相同，则列表中的表尾元素被移动到表头，并返回该元素，可以把这种特殊情况视作列表的旋转(rotation)操作。
     *
     * @param sourceKey   key
     * @param destinationKey   方向
     *
     * @return 被移除元素的数量
     */
    public String rpoplpush(String sourceKey,String destinationKey) {
        if (StringUtil.isValueNull(sourceKey,destinationKey)) {
            return null;
        }
        MultiKeyCommands client = pool.getMultiKeyCommands();
        if(client == null){
            return null;
        }
        try {
            return client.rpoplpush(sourceKey,destinationKey);
        } finally {
            pool.close(client);
        }
    }


    /**
     * BRPOPLPUSH 是 RPOPLPUSH 的阻塞版本，当给定列表 source 不为空时， BRPOPLPUSH 的表现和 RPOPLPUSH 一样。
     * 当列表 source 为空时， BRPOPLPUSH 命令将阻塞连接，直到等待超时，或有另一个客户端对 source 执行 LPUSH 或 RPUSH 命令为止。
     * 超时参数 timeout 接受一个以秒为单位的数字作为值。超时参数设为 0 表示阻塞时间可以无限期延长
     *
     * @param sourceKey   源列表的KEY
     * @param destinationKey   目标链表的KEY
     * @param timeOut   超时时间
     *
     * @return 假如在指定时间内没有任何元素被弹出，则返回一个 nil 和等待时长
     * 反之，返回一个含有两个元素的列表，第一个元素是被弹出元素的值，第二个元素是等待时长。
     */
    public String brpoplpush(String sourceKey,String destinationKey,int timeOut) {
        if (StringUtil.isValueNull(sourceKey,destinationKey)) {
            return null;
        }
        MultiKeyCommands client = pool.getMultiKeyCommands();
        if(client == null){
            return null;
        }
        try {
            return client.brpoplpush(sourceKey,destinationKey,timeOut);
        } finally {
            pool.close(client);
        }
    }

    /**
     * 移除并返回列表 key 的头元素。
     *
     * @param key   key
     *
     * @return 列表的头元素。当 key 不存在时，返回 nil 。
     */
    public String lpop(String key) {
        if (StringUtil.isValueNull(key)) {
            return null;
        }
        JedisCommands client = pool.getClient();
        try {
            return client.lpop(key);
        } finally {
            pool.close(client);
        }
    }

    /**
     * 移除并返回列表 key 的尾元素。
     *
     * @param key   key
     *
     * @return 列表的尾元素。。当 key 不存在时，返回 nil 。
     */
    public String rpop(String key) {
        if (StringUtil.isValueNull(key)) {
            return null;
        }
        JedisCommands client = pool.getClient();
        try {
            return client.rpop(key);
        } finally {
            pool.close(client);
        }
    }

    /**
     * BLPOP 是列表的阻塞式(blocking)弹出原语。
     * 当给定列表内没有任何元素可供弹出的时候，连接将被 BLPOP 命令阻塞，直到等待超时或发现可弹出元素为止。
     * 如果所有给定 key 都不存在或包含空列表，那么 BLPOP 命令将阻塞连接，直到等待超时，或有另一个客户端对给定 key 的任意一个执行 LPUSH 或 RPUSH 命令为止。
     * 相同的 key 可以被多个客户端同时阻塞。
     *
     * 不同的客户端被放进一个队列中，按『先阻塞先服务』(first-BLPOP，first-served)的顺序为 key 执行 BLPOP 命令
     *
     * @param key   key
     * @param timeOut   超时时间 超时参数设为 0 表示阻塞时间可以无限期延长
     *
     * @return 列表的头元素。当 key 不存在时，返回 nil 。
     */
    public String blpop(String key,int timeOut) {
        if (StringUtil.isValueNull(key)) {
            return null;
        }
        JedisCommands client = pool.getClient();
        try {
            List<String> rsList = client.blpop(timeOut,key);
            if(rsList != null && rsList.size() == 2){
                return rsList.get(1);
            }
            return null;
        } finally {
            pool.close(client);
        }
    }

    /**
     *
     * 当给定列表内没有任何元素可供弹出的时候，连接将被 BLPOP 命令阻塞，直到等待超时或发现可弹出元素为止。
     * 如果所有给定 key 都不存在或包含空列表，那么 BLPOP 命令将阻塞连接，直到等待超时，或有另一个客户端对给定 key 的任意一个执行 LPUSH 或 RPUSH 命令为止。
     * 相同的 key 可以被多个客户端同时阻塞。
     *
     * 不同的客户端被放进一个队列中，按『先阻塞先服务』(first-BLPOP，first-served)的顺序为 key 执行 BLPOP 命令
     *
     *
     * @param timeOut   超时时间 超时参数设为 0 表示阻塞时间可以无限期延长
     * @param keys   key多个列表
     *
     * @return 列表的头元素。当 key 不存在时，返回 nil 。
     */
    public String blpop(int timeOut,String... keys) {
        if (StringUtil.isValueNull(keys)) {
            return null;
        }
        MultiKeyCommands client = pool.getMultiKeyCommands();
        if(client == null){
            return null;
        }
        try {
            List<String> rsList = client.blpop(timeOut,keys);
            if(rsList != null && rsList.size() == 2){
                return rsList.get(1);
            }
            return null;
        } finally {
            pool.close(client);
        }
    }

    /**
     * BLPOP 是列表的阻塞式(blocking)弹出原语。
     * 当给定列表内没有任何元素可供弹出的时候，连接将被 BLPOP 命令阻塞，直到等待超时或发现可弹出元素为止。
     * 如果所有给定 key 都不存在或包含空列表，那么 BLPOP 命令将阻塞连接，直到等待超时，或有另一个客户端对给定 key 的任意一个执行 LPUSH 或 RPUSH 命令为止。
     * 相同的 key 可以被多个客户端同时阻塞。
     *
     * 不同的客户端被放进一个队列中，按『先阻塞先服务』(first-BLPOP，first-served)的顺序为 key 执行 BLPOP 命令
     *
     * @param key   key
     * @param timeOut   超时时间 超时参数设为 0 表示阻塞时间可以无限期延长
     *
     * @return 列表的头元素。当 key 不存在时，返回 nil 。
     */
    public String brpop(String key,int timeOut) {
        if (StringUtil.isValueNull(key)) {
            return null;
        }
        JedisCommands client = pool.getClient();
        try {
            List<String> rsList = client.brpop(timeOut,key);
            if(rsList != null && rsList.size() == 2){
                return rsList.get(1);
            }
            return null;
        } finally {
            pool.close(client);
        }
    }

    /**
     *
     * 当给定列表内没有任何元素可供弹出的时候，连接将被 BLPOP 命令阻塞，直到等待超时或发现可弹出元素为止。
     * 如果所有给定 key 都不存在或包含空列表，那么 BLPOP 命令将阻塞连接，直到等待超时，或有另一个客户端对给定 key 的任意一个执行 LPUSH 或 RPUSH 命令为止。
     * 相同的 key 可以被多个客户端同时阻塞。
     *
     * 不同的客户端被放进一个队列中，按『先阻塞先服务』(first-BLPOP，first-served)的顺序为 key 执行 BLPOP 命令
     *
     *
     * @param timeOut   超时时间 超时参数设为 0 表示阻塞时间可以无限期延长
     * @param keys   key多个列表
     *
     * @return 列表的头元素。当 key 不存在时，返回 nil 。
     */
    public String brpop(int timeOut,String... keys) {
        if (StringUtil.isValueNull(keys)) {
            return null;
        }
        MultiKeyCommands client = pool.getMultiKeyCommands();
        if(client == null){
            return null;
        }
        try {
            List<String> rsList = client.brpop(timeOut,keys);
            if(rsList != null && rsList.size() == 2){
                return rsList.get(1);
            }
            return null;
        } finally {
            pool.close(client);
        }
    }

    /**
     * 返回列表 key 中，下标为 index 的元素。
     * 下标(index)参数 start 和 stop 都以 0 为底，也就是说，以 0 表示列表的第一个元素，以 1 表示列表的第二个元素，以此类推。
     * 也可以使用负数下标，以 -1 表示列表的最后一个元素， -2 表示列表的倒数第二个元素，以此类推
     * 如果 key 不是列表类型，返回一个错误
     *
     * @param key   key
     *
     * @return 列表中下标为 index 的元素。如果 index 参数的值不在列表的区间范围内(out of range)，返回 nil 。
     */
    public String lindex(String key,long index) {
        if (StringUtil.isValueNull(key,index)) {
            return null;
        }
        JedisCommands client = pool.getClient();
        try {
            return client.lindex(key,index);
        } finally {
            pool.close(client);
        }
    }

    /**
     * 将列表 key 下标为 index 的元素的值设置为 value 。
     * 当 index 参数超出范围，或对一个空列表( key 不存在)进行 LSET 时，返回一个错误。
     *
     * @param key   key
     *
     * @return 操作成功返回 ok ，否则返回错误信息。
     */
    public String lset(String key,long index,String item) {
        if (StringUtil.isValueNull(key,index,item)) {
            return null;
        }
        JedisCommands client = pool.getClient();
        try {
            return client.lset(key,index,item);
        } finally {
            pool.close(client);
        }
    }


    /**
     * 对一个列表进行修剪(trim)，就是说，让列表只保留指定区间内的元素，不在指定区间之内的元素都将被删除
     * 举个例子，执行命令 LTRIM list 0 2 ，表示只保留列表 list 的前三个元素，其余元素全部删除
     *
     * @param key   key
     * @param start  开始的下标
     * @param stop   结束的下标
     *
     * @return 命令执行成功时，返回 ok
     */
    public String ltrim(String key,long start,long stop) {
        if (StringUtil.isValueNull(key,start,stop)) {
            return null;
        }
        JedisCommands client = pool.getClient();
        try {
            return client.ltrim(key,start,stop);
        } finally {
            pool.close(client);
        }
    }
}
